/**
 *****************************************************************************
 * DESC ：. 
 * Author : Junwei Zhang 
 * CREATEDATE ：2012-6-26 
 * MODIFYLIST ：       	Name 			 Date 			Reason/Contents
 * 				------------------------------------------------------ 
 * 				 	Junwei Zhang     2012-6-26		Created.
 ****************************************************************************
 **/
package com.me.server;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.me.server.JTalkerMember.Message;
import com.me.server.MessageParser.GlobalUser;
import com.me.server.MessageParser.OneMember;

/**
 * 
 * <br/>
 * 上午9:26:59 2012-6-26
 * 
 * @Author <a href="mailto:mestudying@sina.com">Junwei Zhang</a><br/>
 **/
public class JTalkServer {

	private static final int PARAMS_PORT = 4443;
	/** 插件目录 */
	private static final String PARAMS_PLUGIN_DIR = "./plugins";
	/** 聊天室名称前缀 */
	private static final String CHATROOM_MARKER = "#";
	private static final String GLOBAL_USER_LIST = "server_users02.xml";
	/** 日志输出目录. */
	private static String PARAMS_LOGFILE = "D:\\Studying\\workspace\\Chat\\instantmessagesoftware\\F.Development\\Commu_0.0\\com\\me\\doc\\201208\\2012_08_17_log.log";
//	private ServerSocket serverSocket = null;
	// private List<JTalkServerThread> threadList = null;
	private List<GlobalUser> globalUsers = null;
	private List<JTalkerMember> memberList = null;
	/** 插件加载器. */
	private JTalkPlugInloader myPlugInLoader = null;

	// private static String msgSuffix = "【Message From Server】---";
	public static String MESSAGE = "Message";
	public static String VERSION = "0.0.1";
	private Logger log = null;
	private String workDir = "./";
	private UserUpdateTimer uut = null;
	private ServerSocketChannel serverSocketChannel = null;
	private Selector selector = null;
	/* 缓冲区大小 */
	private int BLOCK = 4096;
	/* 接受数据缓冲区 */
	private ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK);
	/* 发送数据缓冲区 */
	private ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);
	
	public JTalkServer(int portNum, String plugInDir, String logFile) {
		// threadList = new ArrayList<JTalkServerThread>();
		// this.messageList = new ArrayList<String>();
		this.globalUsers = new ArrayList<GlobalUser>();
		this.memberList = new ArrayList<JTalkerMember>();
		this.listPlugIns(plugInDir);
		log = new Logger(logFile);
		log.write(" " + this.myPlugInLoader.getLoadedPlugIns());
		if (!this.loadGlobalUsers()) {
			this.log.writeError("Can't load global user file (" + this.workDir + GLOBAL_USER_LIST
					+ "). No user defined.");
			// System.exit(-1);
		}
		this.uut = new UserUpdateTimer(this);
		this.uut.start();
		try {
			// serverSocket = new ServerSocket(portNum);
			// 打开服务器套接字通道
			serverSocketChannel = ServerSocketChannel.open();
			// 服务器配置为非阻塞
			serverSocketChannel.configureBlocking(false);
			// 检索与此通道关联的服务器套接字
			ServerSocket serverSocket = serverSocketChannel.socket();
			// 进行服务的绑定
			serverSocket.bind(new InetSocketAddress("localhost", portNum));
			// 通过open()方法找到Selector
			selector = Selector.open();
			// 注册到selector，等待连接
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 加载已注册的用户.
	 * 
	 * @return
	 */
	private boolean loadGlobalUsers() {
		String path = this.workDir + GLOBAL_USER_LIST;
		File globalUserFile = new File(path);
		MessageParser parser = new MessageParser();
		if (!parser.parsing(globalUserFile)) {
			return false;
		}
		this.globalUsers.clear();
		for (int i = 0; i < parser.getGlobalUserNum(); i++) {
			this.globalUsers.add(parser.getAGlobalUser(i));
		}
		this.log.write("+ Number of global users: " + this.globalUsers.size());
		return true;
	}

	/**
	 * 获取所有的插件.
	 * 
	 * @param plugInDir
	 */
	private void listPlugIns(String plugInDir) {
		System.out.println(" + Using the following plugins:");
		if (this.myPlugInLoader == null) {
			this.myPlugInLoader = new JTalkPlugInloader(this, plugInDir);
		}
		System.out.println(" " + this.myPlugInLoader.getLoadedPlugIns());
	}

	public static void main(String[] args) {
		JTalkServer jTalkServer = new JTalkServer(PARAMS_PORT, PARAMS_PLUGIN_DIR, PARAMS_LOGFILE);
		jTalkServer.start();
	}

	public static String makeHash(String input) {
		try {
			MessageDigest md5 = MessageDigest.getInstance("MD5");
			md5.update(input.getBytes());
			BigInteger hash = new BigInteger(1, md5.digest());
			return hash.toString(16);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return input;
		}

	}

	/**
	 * Start the server.
	 */
	public void start() {
		try {
			// Socket socket = null;
			// ProcessorManager manager = new ProcessorManager();
			// while ((socket = serverSocket.accept()) != null) {
			// MessageProcessor processor = new MessageProcessor(socket);
			// manager.add(processor);
			// manager.run();
			// ThreadPool pool = new ThreadPool(5);
			// pool.addTask(processor);
			// JTalkServerThread serverThread = new JTalkServerThread(this, processor);
			// serverThread.start();
			// }
			// serverSocket.close();
			Runnable r = new Runnable() {
				@Override
				public void run() {
					while (true) {
						// 选择一组键，并且相应的通道已经打开
						try {
							selector.select();
						} catch (IOException e) {
							e.printStackTrace();
							continue;
						}
						// 返回此选择器的已选择键集。
						Set<SelectionKey> selectionKeys = selector.selectedKeys();
						Iterator<SelectionKey> iterator = selectionKeys.iterator();
						while (iterator.hasNext()) {
							SelectionKey selectionKey = iterator.next();
							iterator.remove();
							try {
								handleKey(selectionKey);
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
					}
				}
			};
			new Thread(r).start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 处理请求
	private void handleKey(SelectionKey selectionKey) throws IOException {
		// 接受请求
		JTalkProtocal protocal = null;
		// 测试此键的通道是否已准备好接受新的套接字连接。
		if (selectionKey.isAcceptable()) {
			// 返回为之创建此键的通道。
			ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
			// 接受到此通道套接字的连接。
			// 此方法返回的套接字通道（如果有）将处于阻塞模式。
			SocketChannel socketChannel = server.accept();
			// 配置为非阻塞
			socketChannel.configureBlocking(false);
			// 注册到selector，等待连接
			// client.register(selector, SelectionKey.OP_READ);
			System.out.println("isAcceptable");
			//client.register(selector, SelectionKey.OP_WRITE);
			JTalkerMember m = new JTalkerMember(this, socketChannel);
			protocal = new JTalkProtocal(m);
			m.setProtocal(protocal);
			String sendText = protocal.processInput(null);
			m.setSendText(sendText);
			memberList.add(m);
//			System.out.println("port1:" + socketChannel.socket().getPort() + ":" + sendText);
			socketChannel.register(selector, SelectionKey.OP_WRITE);
		} else if (selectionKey.isReadable()) {
			// 返回为之创建此键的通道。
			SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
			// 将缓冲区清空以备下次读取
			receiveBuffer.clear();
			// 读取服务器发送来的数据到缓冲区中
			int count = socketChannel.read(receiveBuffer);
			if (count > 0) {
				String receiveText = new String(receiveBuffer.array(), 0, count);
				System.out.println("服务器端接受客户端数据--:" + receiveText);
				// 读取数据的时候应该判断一下是从哪个客户端来的信息.
				JTalkerMember m = this.getMember(socketChannel);
				m.setSendText(m.getProtocal().processInput(receiveText));
				System.out.println("服务器端处理后的数据--:" + m.getSendText());
//				socketChannel.register(selector, SelectionKey.OP_WRITE);
			}
		} else if (selectionKey.isWritable()) {
			// 将缓冲区清空以备下次写入
			sendBuffer.clear();
			// 返回为之创建此键的通道。
			SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
			JTalkerMember m = this.getMember(socketChannel);
			String sendText = m.getSendText();
//			System.out.println("port2:" + socketChannel.socket().getPort() + ":" + sendText);
			m.setSendText(null);
			if(sendText != null){
				// sendText = "Test data";
				// 向缓冲区中输入数据
				sendBuffer.put(sendText.getBytes());
				// 将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
				sendBuffer.flip();
				// 输出到通道
				socketChannel.write(sendBuffer);
				System.out.println("服务器端向客户端发送数据--：" + sendText);
				socketChannel.register(selector, SelectionKey.OP_READ);
			}
		}
	}

	/**
	 * 创建一新的用户.
	 * 
	 * @param userName
	 * @param password
	 * @return
	 */
	public boolean addGlobalUser(String userName, String password) {
		// TODO Thread Safety
		if (this.checkIfResgistered(userName)) {
			return false;
		}
		MessageParser parser = new MessageParser();
		this.globalUsers.add(parser.createGlobalUserObject(userName, password, null));
		this.log.write("+ New account successfull created: " + userName + "/" + JTalkServer.makeHash(password));
		return true;
	}

	/**
	 * 校验用户名是否已经存在.
	 * 
	 * @param userName
	 * @return
	 */
	private boolean checkIfResgistered(String userName) {
		if (this.getGlobalUserByName(userName) != null) {
			return true;
		}
		this.log.write("+ Unregistered user tries to connect: " + userName);
		return false;
	}

	public String getXMLMemberList(JTalkerMember caller) {
		// TODO Auto-generated method stub
		String retVal = "";
		for (int i = 0; i < this.memberList.size(); i++) {
			JTalkerMember member = this.memberList.get(i);
			// System.out.println("-----member-----" + member.isChatRoom());
			if (member != null) {
				if (!member.isChatRoom()) {
					retVal += ("<MEMBER name=\"" + member.getUserName() + "\"");
					retVal += (" status=\"" + member.getUserStatus() + "\"");
					retVal += (" ip=\"" + member.getUserIP() + "\"/>");
				} else {
					if (member.isChatRoomUser(caller.getUserName())) {
						retVal += ("<MEMBER name=\"" + member.getUserName() + "\"");
						retVal += (" status=\"" + member.getUserStatus() + "\"");
						retVal += (" type=\"" + JTalkerMember.USER_TYPE_CHATROOM + "\"");
						retVal += (" ip=\"" + member.getUserIP() + "\"/>");
					}
				}
			}
		}
		return retVal;
	}

	public JTalkerMember getMember(SocketChannel socketChannel) {
		Socket socket = socketChannel.socket();
		for (JTalkerMember m : memberList) {
			// System.out.println("-----User Port----" + m.getUserPort());
			if (m.getPort() == socket.getPort()) {
				return m;
			}
		}
		return null;
	}

	/** 向服务器登录人员列表中新增一用户. */
	public JTalkerMember addMember(String userName, String password, SocketChannel socketChannel, String type, String sid) {
		String hostAddress = socketChannel.socket().getInetAddress().getHostAddress();
		String port = String.valueOf(socketChannel.socket().getPort());
		JTalkerMember member = null;
		if (this.checkIfResgistered(userName, password) && !this.isUserOnlineByName(userName)) {
			member = new JTalkerMember(this, userName, hostAddress, port, "normal", type);
			if (this.memberList.add(member)) {
				member = this.getMember(userName);
				member.setUserRole(this.getGlobalUserByName(userName).getRole());
			}
		}
		return member;
	}

	/**
	 * 获取一个注册的用户.
	 * 
	 * @param userName
	 * @return
	 */
	private GlobalUser getGlobalUserByName(String userName) {
		// TODO Auto-generated method stub
		GlobalUser user = null;
		for (GlobalUser u : this.globalUsers) {
			if (userName.equals(u.getName())) {
				user = u;
				return user;
			}
		}
		return null;
	}

	/**
	 * 校验用户是否已在线.
	 * 
	 * @param userName
	 * @return
	 */
	private boolean isUserOnlineByName(String userName) {
		boolean isOnline = false;
		for (JTalkerMember m : memberList) {
			if (m.isLogin() && m.getUserName().equals(userName)) {
				isOnline = true;
			}
		}
		return isOnline;
	}

	/**
	 * 校验用户名和密码是否已注册.
	 * 
	 * @param userName
	 * @param password
	 * @return
	 */
	private boolean checkIfResgistered(String userName, String password) {
		for (GlobalUser u : this.globalUsers) {
			if (u.getName().equals(userName) && u.getPasswd().equals(JTalkServer.makeHash(password))) {
				return true;
			}
		}
		this.log.write("+ Unregistered user tries to connect: " + userName + "/" + password);
		return false;
	}

	/**
	 * 根据用户名获取用户信息.
	 * 
	 * @param userName
	 * @return
	 */
	public JTalkerMember getMember(String userName) {
		for (JTalkerMember m : memberList) {
			if (userName.equals(m.getUserName())) {
				return m;
			}
		}
		return null;
	}

	/**
	 * Send the update message to all active user.
	 * 
	 * @param sender
	 */
	public boolean sendUpdateRequestTo(String users) {
		return this.sendUpdateRequestTo(users.split(","));
	}

	private boolean sendUpdateRequestTo(String[] users) {
		for (String u : users) {
			if (this.getMember(u) != null) {
				this.getMember(u).sendUpdateRequest();
			}
		}
		return true;
	}

	public void sendUpdateRequestToAll(String user) {
		for (JTalkerMember m : memberList) {
			if (m.isLogin() && !m.getUserName().equals(user)) {
				m.sendUpdateRequest();
			}
		}
	}

	/**
	 * 获取插件加载器.
	 * 
	 * @return
	 */
	public JTalkPlugInloader getPlugInLoader() {
		return this.myPlugInLoader;
	}

	/**
	 * 创建聊天室用户.
	 * 
	 * @param string
	 * @return
	 */
	public JTalkerMember addChatRoom(String userName) {
		// TODO Auto-generated method stub
		userName = CHATROOM_MARKER + userName;
		// System.out.println("-----this.memberList.size()-----" + this.memberList.size());
		this.memberList.add(new JTalkerMember(this, userName, null, null, null, JTalkerMember.USER_TYPE_CHATROOM));
		return this.getMember(userName);
	}

	/**
	 * 获取聊天室所有成员.
	 * 
	 * @param roomName
	 * @param caller
	 * @return
	 */
	public String getChatroomUsers(String roomName, JTalkerMember caller) {
		String retVal = "";
		if (!roomName.startsWith(CHATROOM_MARKER)) {
			roomName = CHATROOM_MARKER + roomName;
		}
		JTalkerMember member = this.getMember(roomName);
		if (member == null || !member.isChatRoom()) {
			retVal = "";
		}
		String[] users = member.getChatroomUsers();
		if (users == null) {
			retVal = "";
		}
		for (int i = 0; i < users.length; i++) {
			retVal += users[i];
			if (i < (users.length - 1)) {
				retVal += ";;";
			}
		}
		return retVal;
	}

	/**
	 * 获取所有注册用户.
	 * 
	 * @return
	 */
	public String getAllGlobalUsers() {
		String retVal = "";
		int length = this.globalUsers.size();
		for (int i = 0; i < length; i++) {
			retVal += this.globalUsers.get(i).getName();
			if (i < (length - 1)) {
				retVal += ";;";
			}
		}
		return retVal;
	}

	/**
	 * 向聊天室增加一个用户.
	 * 
	 * @param roomName
	 * @param userName
	 * @param caller
	 * @return
	 */
	public boolean addChatRoomUser(String roomName, String newUser, JTalkerMember caller) {
		// TODO Auto-generated method stub
		if (!roomName.startsWith(CHATROOM_MARKER)) {
			roomName = CHATROOM_MARKER + roomName;
		}
		JTalkerMember member = this.getMember(roomName);
		if (member == null || !member.isChatRoom()
				|| (!member.getChatroomAdmin().equals(caller.getUserName()) && caller.getUserRoleValue() < 1)) {
			return false;
		}
		if (newUser != null) {
			if (member.addChatroomUser(newUser)) {
				this.sendUpdateRequestTo(member.getChatroomUsers());
			} else {
				return false;
			}
		}
		return true;
	}

	/**
	 * 向聊天室中所有成员发送消息.
	 */
	public void redirectChatroomMessages() {
		// TODO Auto-generated method stub
		for (JTalkerMember member : this.memberList) {
			if (member != null && member.isChatRoom()) {
				Message[] msgs = member.getMessages();
				for (String userName : member.getChatroomUsers()) {
					JTalkerMember m = this.getMember(userName);
					if (m != null) {
						m.addMessages(msgs);
					}
				}
			}
		}
	}

	/**
	 * 从聊天室中删除一个成员.
	 * 
	 * @param roomName
	 *            聊天室名称
	 * @param userName
	 *            要删除的聊天室成员名称
	 * @param caller
	 *            发送删除任务的人
	 * @return
	 */
	public boolean removeChatRoomUser(String roomName, String userName, JTalkerMember caller) {
		if (!roomName.startsWith(CHATROOM_MARKER)) {
			roomName += CHATROOM_MARKER + roomName;
		}
		JTalkerMember member = this.getMember(roomName);
		// 仅聊天室管理员有删除的权限
		if (member == null || !member.isChatRoom()
				|| (!member.getChatroomAdmin().equals(caller.getUserName()) && caller.getUserRoleValue() < 1)) {
			return false;
		}
		// 聊天室管理员不能删除自己
		if (member.getChatroomAdmin().equals(userName)) {
			return false;
		}
		if (userName != null) {
			// log.removeUser
			if (member.removeChatrommUser(userName)) {
				this.sendUpdateRequestTo(member.getChatroomUsers());
			} else {
				return false;
			}
		}
		return false;
	}

	/**
	 * 允许的用户名.
	 * 
	 * @param userName
	 * @return
	 */
	public static boolean isAllowedUserName(String userName) {
		if (userName.length() > 10) {
			return false;
		}
		return userName.matches("[A-Za-z0-9-]+");
	}

	/**
	 * 删除聊天室.
	 * 
	 * @param roomName
	 *            聊天室名称.
	 * @param caller
	 *            删除者.
	 * @return
	 */
	public boolean removeChatroom(String roomName, JTalkerMember caller) {
		// TODO Auto-generated method stub
		if (!roomName.startsWith(CHATROOM_MARKER)) {
			roomName = CHATROOM_MARKER + roomName;
		}
		JTalkerMember member = this.getMember(roomName);
		if (member == null) {
			return false;
		}
		if (!member.isChatRoom()
				|| (member.getChatroomAdmin() != null && !member.getChatroomAdmin().equals(caller.getUserName()) && caller
						.getUserRoleValue() < 1)) {
			return false;
		}
		if (this.removeMember(member)) {
			this.sendUpdateRequestToAll(null);
			return true;
		}
		return false;
	}

	/**
	 * 删除一个成员.
	 * 
	 * @param member
	 * @return
	 */
	private boolean removeMember(JTalkerMember member) {
		for (JTalkerMember m : this.memberList) {
			if (m.getUserName().equals(member.getUserName())) {
				this.memberList.remove(m);
				return true;
			}
		}
		return false;
	}

	/**
	 * 获取登录人员数.
	 * 
	 * @return
	 */
	public int getNumOfMembers() {
		return this.memberList.size();
	}

	/**
	 * 根据端口号注销用户.
	 * 
	 * @param userPort
	 *            端口号
	 */
	public boolean removeMember(String userPort) {
		for (JTalkerMember m : this.memberList) {
			if (userPort.equals(m.getUserPort())) {
				this.memberList.remove(m);
				return true;
			}
		}
		return false;
	}

	public class UserUpdateTimer extends Thread {

		private JTalkServer parent = null;
		public boolean terminate = false;
		private int lastLength = 0; // Last number of global users

		public UserUpdateTimer(JTalkServer parent) {
			this.parent = parent;
			this.setName("Server-UserUpdateTimer");
		}

		@Override
		public void run() {
			while (!terminate) {
				if (this.lastLength != this.parent.globalUsers.size()) {
					this.writeXMLFile();
					this.lastLength = this.parent.globalUsers.size();
				}
				// Clean up user List (kill old sessions)
				// this.parent.cleanUpMemberList();
				try {
					Thread.sleep(5000); // Update every 5 seconds.
				} catch (Exception e) {
				}
			}
		}

		private synchronized boolean writeXMLFile() {
			try {
				File outputFile = new File(this.parent.workDir + GLOBAL_USER_LIST);
				FileWriter out = new FileWriter(outputFile);
				// Write Head
				out.write("<?xml version=\"1.0\"?>\n\n<users>\n");
				for (int i = 0; i < this.parent.globalUsers.size(); i++) {
					GlobalUser tmpGU = (GlobalUser) this.parent.globalUsers.get(i);
					out.write("  <global_user name=\"" + tmpGU.getName() + "\" passwd=\"" + tmpGU.getPasswd()
							+ "\" role=\"" + tmpGU.getRole() + "\" />\n");
				}
				out.write("</users>");
				out.close();
				return true;

			} catch (Exception e) {
				e.printStackTrace();
				return false;
			}
		}

	}

	/**
	 * 用户登录.
	 * @param oneMember
	 * @return
	 */
	public boolean login(OneMember oneMember) {
		String userName = oneMember.name;
		String password = oneMember.passwd;
		for (GlobalUser u : this.globalUsers) {
			if(this.checkIfResgistered(userName, password) && !this.isUserOnlineByName(userName)
				&& u.getName().equals(userName) && u.passwd.equals(JTalkServer.makeHash(password))){
				return true;
			}
		}
		return false;
	}

	public void setSocketChannelWritable(SocketChannel socketChannel){
		try {
			socketChannel.register(selector, SelectionKey.OP_WRITE);
		} catch (ClosedChannelException e) {
			e.printStackTrace();
		}
	}

}
